﻿namespace MapleRock.P2PEmulator.RGR
{
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using System.Text;

  using MapleRock.P2PEmulator.Core;

  internal sealed class RgrExperimentRun
  {
    private Network network = null;

    public RgrExperimentCount[] Counts { get; set; }

    public RgrExperimentCount AvgCount { get; private set; }

    public RgrExperimentCount MinCount { get; private set; }

    public RgrExperimentCount MaxCount { get; private set; }

    public void Go(Network network)
    {
      this.network = network;

      ModelOutputFile.Instance.BeginRun();

      this.MakeRun();

      this.EvaluateStats();

      ExperimentLog.Instance.IncrementRunProgress();

      ModelOutputFile.Instance.EndRun();
    }

    private void EvaluateStats()
    {
      this.AvgCount = new RgrExperimentCount();
      this.AvgCount.DeliveryTime = 0;
      this.AvgCount.PeerTrafficStreamIntencity = 0;

      this.MinCount = new RgrExperimentCount();
      this.MinCount.DeliveryTime = double.MaxValue;
      this.MinCount.PeerTrafficStreamIntencity = double.MaxValue;

      this.MaxCount = new RgrExperimentCount();
      this.MaxCount.DeliveryTime = 0;
      this.MaxCount.PeerTrafficStreamIntencity = 0;

      foreach (var peer in this.network.State.Peers.Values)
      {
        ModelOutputFile.Instance.PeerState(peer);

        this.EvaluateStats_Traffic(peer);
        this.EvaluateStats_DeliveryTime(peer);
      }

      this.MinCount.DeliveryTime *= Network.RunDurationMilliseconds;
      this.MaxCount.DeliveryTime *= Network.RunDurationMilliseconds;

      this.AvgCount.DeliveryTime *= Network.RunDurationMilliseconds;
      this.AvgCount.DeliveryTime /= (this.network.State.Peers.Count / 2);

      this.MinCount.PeerTrafficStreamIntencity *= 8.0;
      this.MinCount.PeerTrafficStreamIntencity /= 1024.0;

      this.MaxCount.PeerTrafficStreamIntencity *= 8.0;
      this.MaxCount.PeerTrafficStreamIntencity /= 1024.0;

      this.AvgCount.PeerTrafficStreamIntencity *= 8.0;
      this.AvgCount.PeerTrafficStreamIntencity /= 1024.0;
      this.AvgCount.PeerTrafficStreamIntencity /= (this.network.State.Peers.Count / 2);

      Debug.Assert(
        this.MinCount.DeliveryTime <= this.AvgCount.DeliveryTime,
        "this.MinCount.DeliveryTime <= this.AvgCount.DeliveryTime");
      Debug.Assert(
        this.AvgCount.DeliveryTime <= this.MaxCount.DeliveryTime,
        "this.AvgCount.DeliveryTime <= this.MaxCount.DeliveryTime");
    }

    private void EvaluateStats_DeliveryTime(Peer peer)
    {
      if (peer.SearchRequestCompletionPeriods.Count == 0)
      {
        return;
      }

      var minValue = peer.SearchRequestCompletionPeriods.Min();
      this.MinCount.DeliveryTime = Math.Min(this.MinCount.DeliveryTime, minValue);

      var maxValue = peer.SearchRequestCompletionPeriods.Max();
      this.MaxCount.DeliveryTime = Math.Max(this.MaxCount.DeliveryTime, maxValue);

      var avgValue = peer.SearchRequestCompletionPeriods[0];
      for (var j = 1; j < peer.SearchRequestCompletionPeriods.Count; ++j)
      {
        avgValue += peer.SearchRequestCompletionPeriods[j];
      }

      avgValue /= Convert.ToUInt32(peer.SearchRequestCompletionPeriods.Count);

      this.AvgCount.DeliveryTime += avgValue;
    }

    private void EvaluateStats_Traffic(Peer peer)
    {
      if (peer.TrafficCounter == 0)
      {
        return;
      }

      this.AvgCount.PeerTrafficStreamIntencity += peer.TrafficCounter;

      this.MinCount.PeerTrafficStreamIntencity = Math.Min(
        this.MinCount.PeerTrafficStreamIntencity,
        peer.TrafficCounter);

      this.MaxCount.PeerTrafficStreamIntencity = Math.Max(
        this.MaxCount.PeerTrafficStreamIntencity,
        peer.TrafficCounter);
    }

    private void MakeRun()
    {
      this.network.PrepareRun();

      this.ScheduleProbeMessages();

      this.network.Go(6000);
    }

    private void ScheduleProbeMessages()
    {
      foreach (var peer in this.network.State.Peers.Values)
      {
        peer.Behaviour = PeerBehaviour.ProcessRequests;
      }

      var random = new Random();
      for (uint i = 1; i <= this.network.State.Peers.Count / 2; ++i)
      {
        var fromPeer = this.network.State.Peers[i];

        var randomPeerIndex = Convert.ToUInt32(random.Next(0, this.network.State.Peers.Count)) + 1;
        if (i == randomPeerIndex)
        {
          ++randomPeerIndex;
        }

        var toPeer = this.network.State.Peers[randomPeerIndex];

        var request = new MessageSearchRequest(
          this.network.State.GenerateUniqueId(),
          fromPeer.Address,
          Guid.NewGuid());
        request.Destination = (double[])toPeer.AbsolutePosition.Clone();
        request.From = fromPeer.Id;
        request.SearchRadius = 0.01;
        request.To = toPeer.Id;

        fromPeer.Outgoing.Enqueue(request);

        fromPeer.SearchRequest = request;
        fromPeer.SearchRequestTimestamp = network.State.TimePassedMs;
      }
    }
  }
}
